/*
 * XS interface to the Windows NT EventLog
 * Written by Jesse Dougherty for hip communications
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <wchar.h>

#include "../ppport.h"

#define SETIV(index,value) sv_setiv(ST(index),value)
#define SETPV(index,string) sv_setpv(ST(index),string)
#define SETPVN(index, buffer, length) sv_setpvn(ST(index),(char*)buffer,length)

/* Modified calls go here. */

/* Revamped ReportEvent that doesn't use SIDs. */
typedef struct _EvtLogCtlBuf
{
    DWORD  dwID;			/* id for mem block */
    HANDLE hLog;			/* event log handle */
    BOOL   wideEntries;			/* has unicode character entries */
    LPBYTE BufPtr;			/* pointer to data buffer */
    DWORD  BufLen;			/* size of buffer */
    DWORD  NumEntries;			/* number of entries in buffer */
    DWORD  CurEntryNum;			/* next entry to return */
    EVENTLOGRECORD *CurEntry;		/* point to next entry to return */
    DWORD  Flags;			/* read flags for ReadEventLog */
} EvtLogCtlBuf, *lpEvtLogCtlBuf;

#define EVTLOGBUFSIZE 1024
#define EVTLOGID ((DWORD)0x674c7645L)
#define SVE(x)   (lpEvtLogCtlBuf)(x)

/* constant function for exporting NT definitions for Eventlogs. */

static double
constant(char *name, int arg)
{
    errno = 0;
    switch (*name) {
    case 'A':
    break;
    case 'B':
    break;
    case 'C':
    break;
    case 'D':
    break;
    case 'E':
    if (strEQ(name, "EVENTLOG_AUDIT_FAILURE"))
#ifdef EVENTLOG_AUDIT_FAILURE
        return EVENTLOG_AUDIT_FAILURE;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_AUDIT_SUCCESS"))
#ifdef EVENTLOG_AUDIT_SUCCESS
        return EVENTLOG_AUDIT_SUCCESS;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_BACKWARDS_READ"))
#ifdef EVENTLOG_BACKWARDS_READ
        return EVENTLOG_BACKWARDS_READ;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_END_ALL_PAIRED_EVENTS"))
#ifdef EVENTLOG_END_ALL_PAIRED_EVENTS
        return EVENTLOG_END_ALL_PAIRED_EVENTS;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_END_PAIRED_EVENT"))
#ifdef EVENTLOG_END_PAIRED_EVENT
        return EVENTLOG_END_PAIRED_EVENT;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_ERROR_TYPE"))
#ifdef EVENTLOG_ERROR_TYPE
        return EVENTLOG_ERROR_TYPE;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_FORWARDS_READ"))
#ifdef EVENTLOG_FORWARDS_READ
        return EVENTLOG_FORWARDS_READ;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_INFORMATION_TYPE"))
#ifdef EVENTLOG_INFORMATION_TYPE
        return EVENTLOG_INFORMATION_TYPE;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_PAIRED_EVENT_ACTIVE"))
#ifdef EVENTLOG_PAIRED_EVENT_ACTIVE
        return EVENTLOG_PAIRED_EVENT_ACTIVE;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_PAIRED_EVENT_INACTIVE"))
#ifdef EVENTLOG_PAIRED_EVENT_INACTIVE
        return EVENTLOG_PAIRED_EVENT_INACTIVE;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_SEEK_READ"))
#ifdef EVENTLOG_SEEK_READ
        return EVENTLOG_SEEK_READ;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_SEQUENTIAL_READ"))
#ifdef EVENTLOG_SEQUENTIAL_READ
        return EVENTLOG_SEQUENTIAL_READ;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_START_PAIRED_EVENT"))
#ifdef EVENTLOG_START_PAIRED_EVENT
        return EVENTLOG_START_PAIRED_EVENT;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_SUCCESS"))
#ifdef EVENTLOG_SUCCESS
        return EVENTLOG_SUCCESS;
#else
        goto not_there;
#endif
    if (strEQ(name, "EVENTLOG_WARNING_TYPE"))
#ifdef EVENTLOG_WARNING_TYPE
        return EVENTLOG_WARNING_TYPE;
#else
        goto not_there;
#endif
    break;
    case 'F':
    break;
    case 'G':
    break;
    case 'H':
    break;
    case 'I':
    break;
    case 'J':
    break;
    case 'K':
    break;
    case 'L':
    break;
    case 'M':
    break;
    case 'N':
    break;
    case 'O':
    break;
    case 'P':
    break;
    case 'Q':
    break;
    case 'R':
    break;
    case 'S':
    break;
    case 'T':
    break;
    case 'U':
    break;
    case 'V':
    break;
    case 'W':
    break;
    case 'X':
    break;
    case 'Y':
    break;
    case 'Z':
    break;
    }
    errno = EINVAL;
    return 0;

not_there:
    errno = ENOENT;
    return 0;
}

MODULE = Win32::EventLog	PACKAGE = Win32::EventLog

PROTOTYPES: DISABLE

bool
WriteEventLog(server, source, eventType, category, eventID, reserved, data, ...)
    char *server
    char *source
    DWORD eventType
    DWORD category
    DWORD eventID
    DWORD reserved = NO_INIT
    char *data = NO_INIT
CODE:
{
    int index;
    unsigned int bufLength, dataLength;
    char *buffer, **array;
    HANDLE hLog;

    /* supress unused argument warnings */
    (void)reserved;

    RETVAL = FALSE;

    if (server && *server == '\0')
        server = NULL;

    hLog = RegisterEventSourceA(server, source);
    if (hLog != NULL) {
	data = SvPV(ST(6), dataLength);
        New(3101, array, items - 7, char*);
        for (index = 0; index < items - 7; ++index) {
            buffer = SvPV(ST(index+7), bufLength);
            array[index] = buffer;
        }
        RETVAL = ReportEventA(
            hLog,                /* handle returned by RegisterEventSource */
            (WORD)SvIV(ST(2)),   /* event type to log */
            (WORD)SvIV(ST(3)),   /* event category */
            SvIV(ST(4)),         /* event identifier */
            NULL,                /* user security identifier (optional) */
            (WORD)(items - 7),   /* number of strings to merge with message */
            dataLength,          /* size of raw (binary) data (in bytes) */
            (const char**)array, /* array of strings to merge with message */
            data                 /* address of binary data */
            );
        Safefree(array);
        DeregisterEventSource(hLog);
    }
}
OUTPUT:
    RETVAL

bool
ReadEventLog(handle,Flags,Record,evtHeader,sourceName,computerName,sid,data,strings)
    size_t handle
    DWORD Flags
    DWORD Record
    char *evtHeader = NO_INIT
    char *sourceName = NO_INIT
    char *computerName = NO_INIT
    char *sid = NO_INIT
    char *data = NO_INIT
    char *strings = NO_INIT
CODE:
{
    int length;
    lpEvtLogCtlBuf lpEvtLog;
    BOOL result;

    /* supress unused argument warnings */
    (void)data;
    (void)sid;
    (void)strings;
    (void)computerName;
    (void)evtHeader;
    (void)sourceName;

    RETVAL = FALSE;

    lpEvtLog = SVE(handle);
    if ((lpEvtLog != NULL) && (lpEvtLog->dwID == EVTLOGID)) {
	DWORD NumRead, Required;
	if (Flags != lpEvtLog->Flags) {
	    /* Reset to new read mode & force a re-read call */
	    lpEvtLog->Flags      = Flags;
	    lpEvtLog->NumEntries = 0;
	}
	if ((lpEvtLog->NumEntries == 0) || (Record != 0)) {
	redo_read:
            result = ReadEventLogA(lpEvtLog->hLog, Flags, Record,
                                   lpEvtLog->BufPtr, lpEvtLog->BufLen,
                                   &NumRead, &Required);
            lpEvtLog->wideEntries = FALSE;

	    if (result)
		lpEvtLog->NumEntries = NumRead;
	    else {
		lpEvtLog->NumEntries = 0;
		if (Required > lpEvtLog->BufLen
		    && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
		    lpEvtLog->BufLen = Required*2;
		    Renew(lpEvtLog->BufPtr, lpEvtLog->BufLen, BYTE);
		    goto redo_read;
		}
	    }
	    lpEvtLog->CurEntryNum = 0;
	    lpEvtLog->CurEntry    = (EVENTLOGRECORD*)lpEvtLog->BufPtr;
	}

	if (lpEvtLog->CurEntryNum < lpEvtLog->NumEntries) {
	    EVENTLOGRECORD *LogBuf;
	    LogBuf = lpEvtLog->CurEntry;
	    SETPVN(3, (char*)LogBuf, LogBuf->Length);
	    if (lpEvtLog->wideEntries) {
		LPWSTR pwStr;
		char szBuffer[MAX_PATH+1];
		pwStr = (LPWSTR)(((LPSTR)LogBuf)+sizeof(EVENTLOGRECORD));
		W2AHELPER(pwStr, szBuffer, sizeof(szBuffer));
		SETPV(4, szBuffer);
		pwStr += lstrlenW(pwStr)+1; /* step over NULL */
		W2AHELPER(pwStr, szBuffer, sizeof(szBuffer));
		SETPV(5, szBuffer);
	    }
	    else {
		char *name;
		name = ((LPSTR)LogBuf)+sizeof(EVENTLOGRECORD);
		SETPV(4, name);
		name += strlen(name)+1; /* step over NULL */
		SETPV(5, name);
	    }
	    SETPVN(6, ((LPBYTE)LogBuf)+LogBuf->UserSidOffset, LogBuf->UserSidLength);
	    SETPVN(7, ((LPBYTE)LogBuf)+LogBuf->DataOffset, LogBuf->DataLength);
	    if (lpEvtLog->wideEntries) {
		char* ptr = NULL;
		// calculate the length of the strings
		length = LogBuf->DataOffset-LogBuf->StringOffset;
		if (length) {
		    New(0, ptr, length, char);
                    length = W2AHELPER_LEN((LPWSTR)(((LPBYTE)LogBuf)+LogBuf->StringOffset),
                                           length/sizeof(WCHAR), ptr, length);
		}
		if (length)
		    SETPVN(8, ptr, length-1);
		else
		    SvOK_off(ST(8));
		Safefree(ptr);
	    }
	    else
		SETPVN(8, ((LPBYTE)LogBuf)+LogBuf->StringOffset, LogBuf->DataOffset-LogBuf->StringOffset);

	    /* to next entry in buffer */
	    lpEvtLog->CurEntryNum += LogBuf->Length;
	    lpEvtLog->CurEntry = (EVENTLOGRECORD*)(((LPBYTE)LogBuf) + LogBuf->Length);
	    if (lpEvtLog->CurEntryNum == lpEvtLog->NumEntries) {
		lpEvtLog->NumEntries  = 0;
		lpEvtLog->CurEntryNum = 0;
		lpEvtLog->CurEntry    = NULL;
	    }
	    RETVAL = TRUE;
	}
    }
}
OUTPUT:
    RETVAL

bool
GetEventLogText(source,id,longstring,numstrings,message)
    char *source
    DWORD id
    char *longstring
    int numstrings
    char *message = NO_INIT
CODE:
{
    HINSTANCE dll = NULL;
    HKEY hk;
    int length = SvCUR(ST(2))+1;

    /* XXX TODO:
     * XXX ParameterMessageFile can also be a semicolon separated list of files
     * XXX What about expanding the category id to a string?
     * XXX Determining the log category is kind of bogus!
     * XXX This should have been a parameter.
     */

    static const char *EVFILE[] = {"System", "Security", "Application"};
    char *MsgBuf, **strings, *ptr, *tmpx;
    char msgfile[MAX_PATH], regPath[MAX_PATH];
    DWORD i, id2, maxinsert;
    BOOL result;
    LONG lResult;
    unsigned short j;
    char *percent;
    int percentLen, msgLen, gotPercent;

    /* XXX this seems bogus... */
    message = NULL;

    /* Which EventLog are we reading? */
    for (j=0; j < (sizeof(EVFILE)/sizeof(EVFILE[0])); j++) {
        sprintf(regPath,
                "SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s",
                EVFILE[j], source);
        if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, regPath,
                          0, KEY_READ, &hk) == ERROR_SUCCESS)
        {
            break;
        }
    }

    if (j >= (sizeof(EVFILE)/sizeof(EVFILE[0])))
    XSRETURN_NO;

        /* Get the (list of) message file(s) for this entry */
    i = sizeof(regPath);
    lResult = RegQueryValueExA(hk, "EventMessageFile", 0, 0,
                               (unsigned char *)regPath, &i);
    if (lResult != ERROR_SUCCESS) {
        RegCloseKey(hk);
        XSRETURN_NO;
    }

    if (ExpandEnvironmentStringsA(regPath, msgfile, sizeof(msgfile)) == 0) {
        RegCloseKey(hk);
        XSRETURN_NO;
    }

    /* Try to retrieve message *without* expanding the inserts yet */
    ptr = msgfile;
    while (ptr && !message) {
        char *semi = strchr(ptr, ';');
        if (semi)
            *semi++ = '\0';
        dll = LoadLibraryExA(ptr, 0, LOAD_LIBRARY_AS_DATAFILE);
        if (dll) {
            FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                           FORMAT_MESSAGE_FROM_HMODULE    |
                           FORMAT_MESSAGE_IGNORE_INSERTS,
                           dll, id, 0, (LPSTR)&message, 0, NULL);
            FreeLibrary(dll);
            dll = NULL;
        }
        ptr = semi;
    }
    if (!message) {
        RegCloseKey(hk);
        XSRETURN_NO;
    }

    /* Determine higest %n insert number */
    maxinsert = numstrings;
    ptr = message;
    while ((percent=strchr(ptr, '%'))
           && sscanf(percent, "%%%d", &id2) == 1)
    {
        if (id2 > maxinsert)
            maxinsert = id2;
        ptr = percent + 1;
    }

    New(0, strings, maxinsert, char*);

        /* Allocate dummy strings for inserts not provided by caller */
    for (j=numstrings; j<maxinsert; ++j) {
        New(0, tmpx, 10, char);
        sprintf(tmpx, "%%%d", j+1);
        strings[j] = tmpx;
    }

    i = sizeof(regPath);	/* Fixed */

    ptr = longstring;
    for (j=0; j<numstrings; j++) {
        strings[j] = ptr;
        ptr += strlen(ptr)+1;
        gotPercent = -1;
        while ((percent=strchr(strings[j], '%'))
               && sscanf(percent, "%%%%%d", &id2) == 1)
        {
            gotPercent++;
            if (!dll) {		/* first time round - load dll */
                char paramfile[MAX_PATH];

                if (RegQueryValueExA(hk, "ParameterMessageFile", 0, 0,
                                     (unsigned char *)regPath,
                                     &i) != ERROR_SUCCESS)
                {
                    RegCloseKey(hk);
                    XSRETURN_NO;
                }

                if (ExpandEnvironmentStringsA(regPath, paramfile,
                                              sizeof(paramfile)) == 0)
                {
                    RegCloseKey(hk);
                    XSRETURN_NO;
                }

                dll = LoadLibraryExA(paramfile, 0, LOAD_LIBRARY_AS_DATAFILE);

                if (!dll) {
                    RegCloseKey(hk);
                    XSRETURN_NO;
                }
            }

            if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER
                               | FORMAT_MESSAGE_FROM_HMODULE
                               | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                               dll, id2, 0, (LPSTR)&MsgBuf, 0,
                               (va_list*)&strings[j]) == 0)
            {
                FreeLibrary(dll);
                RegCloseKey(hk);
                XSRETURN_NO;
            }

            percentLen = 2;	/* for %% */
            do {
                percentLen++;
            } while (id2/=10);	/* compute length of %%xxx string */

            msgLen = strlen(MsgBuf);
            Newz(0, tmpx, strlen(strings[j])+msgLen-percentLen+1, char);
            strncpy(tmpx, strings[j], percent-strings[j]);
            strncat(tmpx, MsgBuf,
                    msgLen - ((strcmp(MsgBuf+msgLen-2, "\r\n")==0) ? 2 : 0));
            strcat(tmpx, percent+percentLen);
            if (gotPercent)
                Safefree(strings[j]);
            strings[j] = tmpx;
            LocalFree(MsgBuf);
        }
    }

    RegCloseKey(hk);
    if (dll) {
        FreeLibrary(dll); /* in case it was used above */
        dll = NULL;
    }

    /* XXX 'strings' argument may be broken on 64-bit
	     * platforms since the documentation says 32-bit
	     * values are required */
    result = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                            FORMAT_MESSAGE_FROM_STRING |
                            FORMAT_MESSAGE_ARGUMENT_ARRAY,
                            message, 0, 0, (LPSTR)&MsgBuf, 0,
                            (va_list*)strings) > 0;

    LocalFree(message);

    for (j=0; j<maxinsert; j++)
    if (strings[j] < longstring || strings[j] >= longstring+length)
    Safefree(strings[j]);

    Safefree(strings);

    if (!result || !MsgBuf) {
        XSRETURN_NO;
    }

    SETPV(4, MsgBuf);
    LocalFree(MsgBuf);

    XSRETURN_YES;
}
OUTPUT:
    RETVAL

double
constant(name,arg)
    char *          name
    int             arg

bool
BackupEventLog(hEventLog,lpszBackupFileName)
    void *    hEventLog
    char *    lpszBackupFileName
    CODE:
{
    lpEvtLogCtlBuf lpEvtLog = SVE(hEventLog);
    RETVAL = FALSE;
    if ((lpEvtLog != NULL) && (lpEvtLog->dwID == EVTLOGID))
        RETVAL = BackupEventLogA(lpEvtLog->hLog, lpszBackupFileName);
}
OUTPUT:
    RETVAL

bool
ClearEventLog(hEventLog,lpszBackupFileName)
    void *    hEventLog
    char *    lpszBackupFileName
 CODE:
{
    BOOL result;
    lpEvtLogCtlBuf lpEvtLog = SVE(hEventLog);
    RETVAL = FALSE;
    if ((lpEvtLog != NULL) && (lpEvtLog->dwID == EVTLOGID)) {
        result = ClearEventLogA(lpEvtLog->hLog, lpszBackupFileName);
	if (result && CloseEventLog(lpEvtLog->hLog)) {
	    if (lpEvtLog->BufPtr)
		Safefree(lpEvtLog->BufPtr);
	    Safefree(lpEvtLog);
	    RETVAL = TRUE;
	}
    }
}
OUTPUT:
    RETVAL

bool
CloseEventLog(hEventLog)
    void *    hEventLog
CODE:
{
    lpEvtLogCtlBuf lpEvtLog = SVE(hEventLog);
    RETVAL = FALSE;
    if ((lpEvtLog != NULL) && (lpEvtLog->dwID == EVTLOGID) &&
	CloseEventLog(lpEvtLog->hLog))
    {
	if (lpEvtLog->BufPtr)
	    Safefree(lpEvtLog->BufPtr);
	Safefree(lpEvtLog);
	RETVAL = TRUE;
    }
}
OUTPUT:
    RETVAL

bool
DeregisterEventSource(hEventLog)
    void *    hEventLog

bool
GetNumberOfEventLogRecords(hEventLog,dwRecords)
    void *     hEventLog
    DWORD dwRecords = NO_INIT
CODE:
{
    lpEvtLogCtlBuf lpEvtLog = SVE(hEventLog);
    RETVAL = FALSE;
    if ((lpEvtLog != NULL) && (lpEvtLog->dwID == EVTLOGID) &&
	GetNumberOfEventLogRecords(lpEvtLog->hLog, &dwRecords))
    {
	RETVAL = TRUE;
    }
}
OUTPUT:
    RETVAL
    dwRecords	if (RETVAL) SETIV(1, dwRecords);

bool
GetOldestEventLogRecord(hEventLog,dwOldestRecord)
    void *    hEventLog
    DWORD     dwOldestRecord = NO_INIT
CODE:
{
    lpEvtLogCtlBuf lpEvtLog = SVE(hEventLog);
    RETVAL = FALSE;
    if ((lpEvtLog != NULL) && (lpEvtLog->dwID == EVTLOGID) &&
	GetOldestEventLogRecord(lpEvtLog->hLog, &dwOldestRecord))
    {
	RETVAL = TRUE;
    }
}
OUTPUT:
    RETVAL
    dwOldestRecord	if (RETVAL) SETIV(1, dwOldestRecord);

bool
OpenBackupEventLog(hEventLog,lpszUNCServerName,lpszFileName)
    size_t       hEventLog = 0;
    char *    lpszUNCServerName
    char *    lpszFileName
CODE:
{
    lpEvtLogCtlBuf lpEvtLog;
    New(1908, lpEvtLog, 1, EvtLogCtlBuf);
    lpEvtLog->BufLen = EVTLOGBUFSIZE;
    New(1908, lpEvtLog->BufPtr, lpEvtLog->BufLen, BYTE);
    RETVAL = FALSE;
    lpEvtLog->hLog = OpenBackupEventLogA(lpszUNCServerName,lpszFileName);
    if (lpEvtLog->hLog) {
	/* return info... */
	lpEvtLog->dwID          = EVTLOGID;
	lpEvtLog->NumEntries    = 0;
	lpEvtLog->CurEntryNum   = 0;
	lpEvtLog->CurEntry      = NULL;
	lpEvtLog->Flags         = 0;
	hEventLog = (size_t)lpEvtLog;
	RETVAL = TRUE;
    }
    else {
	/* Open failed... */
	if (lpEvtLog->BufPtr)
	    Safefree(lpEvtLog->BufPtr);
	Safefree(lpEvtLog);
    }
}
OUTPUT:
    RETVAL
    hEventLog

bool
OpenEventLog(hEventLog,lpszUNCServerName,lpszSourceName)
    size_t       hEventLog = 0;
    char *    lpszUNCServerName
    char *    lpszSourceName
CODE:
{
    lpEvtLogCtlBuf lpEvtLog;
    New(1908, lpEvtLog, 1, EvtLogCtlBuf);
    lpEvtLog->BufLen = EVTLOGBUFSIZE;
    New(1908, lpEvtLog->BufPtr, lpEvtLog->BufLen, BYTE);
    RETVAL = FALSE;
    lpEvtLog->hLog = OpenEventLogA(lpszUNCServerName,lpszSourceName);
    if (lpEvtLog->hLog) {
	/* return info... */
	lpEvtLog->dwID          = EVTLOGID;
	lpEvtLog->NumEntries    = 0;
	lpEvtLog->CurEntryNum   = 0;
	lpEvtLog->CurEntry      = NULL;
	lpEvtLog->Flags         = 0;
	hEventLog = (size_t)lpEvtLog;
	RETVAL = TRUE;
    }
    else {
	/* Open failed... */
	if (lpEvtLog->BufPtr)
	    Safefree(lpEvtLog->BufPtr);
	Safefree(lpEvtLog);
    }
}
OUTPUT:
    RETVAL
    hEventLog

void*
RegisterEventSource(lpszUNCServerName,lpszSource)
    char *    lpszUNCServerName
    char *    lpszSource

